import datetime
from datetime import date
import requests
from dateutil.relativedelta import relativedelta
import re
import base64

debug = False
user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69 '
username = 'script'
# password = '50464626@Ebscn'
password = '123456a.'
# bct服务
bct_url = "http://10.1.6.80"


def recur_build_trade(req_body, param):
    trade = {}
    for k, v in req_body.items():
        trade[k] = v
    for k, v in param.items():
        if isinstance(v, dict):
            trade[k] = v.get('value', None)
        elif isinstance(v, list):
            if k not in trade:
                trade[k] = []
            for i, e in enumerate(v):
                if i < len(trade[k]):
                    trade[k][i].update(recur_build_trade({}, e))
                else:
                    trade[k].append(recur_build_trade({}, e))

    return trade


def new_bct_pricer(product_arg, pricing_arg):
    token = _get_token(username, password, bct_url)
    headers = {"Authorization": "Bearer " + token}

    product_info = dict({
        'initialDate': str(date.today()),
        'notional': 10000,
        'annualized': True,
        'annualizedDays': 365,
        'direction': 'SELLER',
        # 'instrumentId': '000905.SH',
        'instrumentId': '000000.BB',
        'initialPrice': 1,
        'strikePriceRatio': 1,
        'strikePriceRatio2': 1,
        'minimumCouponRate': 0,
        'knockInPriceType': 'CLOSE',
        'knockOutPriceType': 'CLOSE',
        'knockOutPaymentType': 'IMMEDIATE_PAYMENT',
    }, **product_arg)

    start_date = datetime.datetime.strptime(product_info.get('initialDate'), '%Y-%m-%d').date()
    print("定价日期为: " + str(start_date))
    # 产品期限: 期末-期初
    end_date = start_date
    term_d = 0
    if pricing_arg.get('term')[-1] == "M":
        end_date = start_date + relativedelta(months=+int(pricing_arg.get('term')[:-1]))
        term_d = (end_date - start_date).days
    elif pricing_arg.get('term')[-1] == "D":
        end_date = start_date + relativedelta(days=+int(pricing_arg.get('term')[:-1]))
        term_d = int(pricing_arg.get('term')[:-1])
    else:
        print("不支持的产品期限格式")
        exit(1)

    # 定价时间默认今天
    req_body = {
        "r": pricing_arg.get('r'),
        "q": pricing_arg.get('q'),
        "v": pricing_arg.get('vol'),
        "spot": pricing_arg.get('k'),
        "notional": product_info.get('notional'),
        "dueDate": str(end_date),
        "annualized": product_info.get('annualized'),
        "annualizedDays": product_info.get('annualizedDays'),
        "direction": product_info.get('direction'),
        "calendarNames": [],
        "businessType": "OPTION",
        "tradeBookingCategory": "EQUITY",
        "products": [{
            'lastObservationDate': str(end_date),
            'dueDate': str(end_date),
            'settlementDate': str(end_date),
            'participation': pricing_arg.get('pr_rate'),
            "calendarNames": [],
            "valuationDate": str(start_date),
            "tradeBookingCategory": "EQUITY",
            "businessType": "OPTION",
            "r": pricing_arg.get('r'),
            "q": pricing_arg.get('q'),
            "v": pricing_arg.get('vol'),
            "spot": pricing_arg.get('k'),
            "customize": {},
        }],
        "performanceGuarantee": {},
        "requiredValidate": "FORMAL"
    }
    req_body.get('products')[0].update(product_info)
    # 计算参数
    if debug:
        print('req_body:', req_body)
    resp_json = requests.post(url=f'{bct_url}/bct/trade-service/calcPricingTradeAndValidate', json=req_body,
                              headers=headers).json()
    if debug:
        print('req_body -> resp_json:', resp_json)

    # 定价
    req_pricing = {
        'pricingEnvironmentName': pricing_arg['pricing_environment'],
        'trades': [recur_build_trade(req_body, resp_json['resultMap'])],
        'valuationDate': str(start_date)
    }
    if debug:
        print('req_pricing:', req_pricing)
    resp_json = requests.post(url=f'{bct_url}/bct/trade-service/trySettlePricing', json=req_pricing,
                              headers=headers).json()
    if debug:
        print('req_pricing -> resp_json:', resp_json)
    if 'errorMessage' in resp_json:
        print('errorMessage: {}'.format(resp_json['errorMessage']))
    if 'summary' not in resp_json:
        exit(1)
    print(resp_json['details'])
    npv = resp_json['summary'].get("pv")
    print('结构类型[%s],npv=> %s' % (product_info.get("productTypeName"),npv))
    return resp_json


def login(username: str, password: str, host: str, port: int, method: str = None) -> dict:
    """
    登陆 - 获取token
    :param username: 登陆用户名
    :param password: 登陆密码
    :param host: 服务器地址
    :param port: 服务器端口
    :param method: 如果设为base64的话，会对用户名密码进行base64
    :return: 登陆头，包含了访问所需的token
    """
    url = '%s/auth-service/users/login' % url_builder(host, port)
    user = username
    pwd = password
    print("login with %s" % (user))
    if method == 'base64':
        user = base64.b64encode(username.encode(encoding='utf-8')).decode(
            encoding='utf-8')
        pwd = base64.b64encode(password.encode(encoding='utf-8')).decode(encoding='utf-8')
    body = {
        'username': user,
        'password': pwd
    }
    res = requests.post(url, json=body)
    try:
        json = res.json()
        print("login success")
        print("token:%s" % json['result']['token'])
    except Exception as e:
        print(e)
        print('\t=>' + res.text)
        print(res.headers)
        raise RuntimeError('error logging in: ')
    if 'error' in json:
        raise RuntimeError('error logging in: ' + json['error']['message'])
    token = json['result']['token']
    headers = {
        'Authorization': 'Bearer ' + token
    }
    return headers


def _get_token(auth_username: str, auth_password: str, server_url: str,
               base64_encode: bool = True) -> str:
    auth_url = "%s%s" % (server_url, '/api/auth-service/users/login')

    if base64_encode:
        usr_name = base64.b64encode(auth_username.encode())
        usr_pwd = base64.b64encode(auth_password.encode())
    else:
        usr_name = auth_username
        usr_pwd = auth_password

    auth_body = {
        'userName': usr_name.decode(),
        'password': usr_pwd.decode()
    }
    headers = {'User-Agent': user_agent}
    auth = requests.post(auth_url, json=auth_body, headers=headers)
    try:
        _token = auth.json()['token']
        return _token
    except Exception:
        error_message = auth.json()['errorMessage']
        raise ValueError(error_message)


def url_builder(host: str, port: int = 80) -> str:
    """
    按照host和port构建一个标准url
    :param host:
    :param port:
    :return:
    """
    protocol_prefix = re.match(r'^(http|https)://[^\n\r]+', host)
    if protocol_prefix:
        return '%s:%s' % (host, port)
    else:
        protocol = 'http'
        if port == 443:
            protocol = 'https'
        return '%s://%s:%s' % (protocol, host, port)


if __name__ == '__main__':
    european_vanilla = {
        'productTypeName': '欧式香草',
        'productType': 'VANILLA_EUROPEAN',  # 结构类型: 欧式香草
        'callOrPut': 'CALL'  # CALL 看涨/PUT 看跌
    }
    american_vanilla = {
        'productTypeName': '美式香草',
        'productType': 'VANILLA_AMERICAN',  # 结构类型: 美式香草
        'callOrPut': 'CALL'  # CALL 看涨/PUT 看跌
    }
    snowball_floating_call = {
        'productTypeName': '保本雪球看涨',
        'productType': 'SNOWBALL_FLOATING_CALL',  # 结构类型: 保本雪球看涨
        'snowBallCouponRate': 0.01  # 红利票息
    }
    snowball_floating_put = {
        'productTypeName': '保本雪球看跌',
        'productType': 'SNOWBALL_FLOATING_PUT',    # 结构类型: 保本雪球看跌
        'snowBallCouponRate': 0.01  # 红利票息
    }
    snowball_floating_partial = {
        'productTypeName': '部分保本雪球',
        'productType': 'SNOWBALL_FLOATING_PARTIAL',  # 结构类型: 部分保本雪球
        'knockInPriceRatio': 0.75,  # 敲入价格系数
        'knockInMinCouponRate': 0.05,  # 敲入最低基准收益率
        'snowBallCouponRate': 0.01,  # 红利票息
        'knockedIn': False  # 是否已敲入
    }
    snowball_floating_none = {
        'productTypeName': '非保本雪球',
        'productType': 'SNOWBALL_FLOATING_NONE',  # 结构类型: 非保本雪球
        'knockInPriceRatio': 0.75,  # 敲入价格系数
        'snowBallCouponRate': 0.01,  # 红利票息
        'knockedIn': False  # 是否已敲入
    }
    snowball_floating_none_step_knock_in = {
        'productTypeName': '阶梯敲入非保本雪球',
        'productType': 'SNOWBALL_FLOATING_NONE_STEP_KNOCK_IN',  # 结构类型: 阶梯敲入非保本雪球
        'snowballKnockInSteps': [  # 阶梯敲入区间信息
            {
                'stepStartDate': '2022-12-01',  # 敲入起始日
                'stepEndDate': '2023-01-01',  # 敲入到期日
                'knockInPriceRatio': 0.7  # 敲入价格系数
            },
            {'stepStartDate': '2023-01-02', 'stepEndDate': '2023-02-01', 'knockInPriceRatio': 0.6},
        ],

        'snowBallCouponRate': 0.01,  # 红利票息
        'knockedIn': False  # 是否已敲入
    }
    shark_fin = {
        'productTypeName': '单鲨',
        'productType': 'SHARK_FIN',  # 结构类型: 单鲨
        'knockOutPriceRatio': 1.1,  # 敲出价格系数
        'knockOutCouponRate': 0.1  # 敲出收益率
    }
    double_shark_fin = {
        'productTypeName': '双鲨',
        'productType': 'DOUBLE_SHARK_FIN',  # 结构类型: 双鲨
        'participation2': 1,  # 参与率2
        'knockOutPriceRatio': 1.1,  # 敲出价格系数
        'knockOutCouponRate': 0.1,  # 敲出收益率
        'knockOutPriceRatio2': 1.1,  # 敲出价格系数2
        'knockOutCouponRate2': 0.1,  # 敲出收益率2
    }
    digital_european = {
        'productTypeName': '欧式二元',
        'productType': 'DIGITAL_EUROPEAN'  # 结构类型: 欧式二元
    }
    airbag_vanilla = {
        'productTypeName': '安全气囊看涨',
        'productType': 'AIRBAG_VANILLA',  # 结构类型: 安全气囊看涨
        'participation2': 1,  # 参与率2
        'participation3': 1,  # 参与率3
        'knockInPriceRatio': 0.75,  # 敲入价格系数
        'knockedIn': False  # 是否已敲入
    }

    # 定价参数
    pricing_info = {
        'pricing_environment': '同余交易环境',  # 定价环境
        'term': '12M',  # 产品期限
        'pr_rate': 1,  # 参与率
        'k': 10,  # 标的物价格
        'vol': 3,
        'r': 4,
        'q': 5
    }

    # 雪球敲出列表生成规则
    knock_rule = {
        'lockupPeriod': 1,  # 锁定期 1代表一个月
        'knockOutPriceStepByStepAdjustmentRatio': 0,  # 敲出价格逐级下降系数
        'freq': '1M',  # 敲出观察频率
        'knockOutPriceRatio': 1,  # 敲出价格系数
        'knockOutCouponRate': 0.03  # 敲出收益率
    }

    # 定价
    new_bct_pricer(dict(snowball_floating_call, **knock_rule), pricing_info)
